/*
 *
 *  Copyright (C) 2010 Tal Eisenberg
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package io;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import net.java.games.input.*;
import java.util.Timer;
import java.util.TimerTask;


import net.java.games.input.Controller.Type;
import tal.drivechain.chain.AbortChainException;
import tal.drivechain.wildcard.Wildcard;
import tal.drivechain.parsing.ParsingAid;
import tal.drivechain.plugin.Channel;
import tal.drivechain.plugin.Module;
import tal.drivechain.variable.types.DataType;
import tal.drivechain.variable.SetProperty.Flag;
import tal.drivechain.variable.VarTableAction;
import tal.drivechain.variable.Variable;

/**
 *
 * @author Tal Eisenberg
 */
public class HIDIn extends Module implements HIDListener {
    private String deviceId = null;
    private int deviceIndex = -1;
    private Timer timer;
    private Variable varValue, varRelative, varAnalog, varId;
    private VarTableAction setValue, setRelative, setAnalog, setId;
    private List<Object[]> triggerMessage;

    @Override
    public void setupProperties() throws AbortChainException {
        varValue = new Variable("value", DataType.FLOAT, null, true);
        setValue = new VarTableAction("value", VarTableAction.Action.SET_ANY);
        varRelative = new Variable("relative", DataType.BOOLEAN, null, true);
        setRelative = new VarTableAction("relative", VarTableAction.Action.SET_ANY);
        varAnalog = new Variable("analog", DataType.BOOLEAN, null, true);
        setAnalog = new VarTableAction("analog", VarTableAction.Action.SET_ANY);
        varId = new Variable("id", DataType.STRING, null, true);
        setId = new VarTableAction("id", VarTableAction.Action.SET_ANY);
        
        setDeviceId(props.getProperty("deviceId"), null);
        setTriggerMessage(props.getProperty("triggerMessage"), null);
    }

    public void setDeviceId(Object value, Flag flag) {
        deviceId = (String)value;
        if (ParsingAid.isInt(deviceId)) {
            deviceIndex = Integer.parseInt(deviceId);
        } else {
            deviceIndex = findIndexByName(deviceId);
        }
        if (this.isRunning()) {
            this.stop();
            this.start();
        }
    } public Object getDeviceId() { return deviceId; }

    public void setTriggerMessage(Object value, Flag flag) {
        List<Object[]> newTrigMsg = (List<Object[]>)value;

        if (flag == Flag.ADD) {
            if (triggerMessage==null) triggerMessage = new ArrayList<Object[]>();
            triggerMessage.addAll(newTrigMsg);
        } else {
            triggerMessage = newTrigMsg;
        }
    } public Object getTriggerMessage() { return triggerMessage; }

    @Override
    public void start() {
        super.start();
        if (deviceIndex!=-1) {
            try {
                Controller hid = ControllerEnvironment.getDefaultEnvironment().getControllers()[deviceIndex];
                if (hid==null) { throw new IllegalArgumentException("Can't open HID device " + deviceId + "(" + deviceIndex + ")"); }
                HIDUpdater updater = new HIDUpdater(hid);
                updater.addListener(this);
                timer = new Timer();
                timer.scheduleAtFixedRate(updater, 0, 10);
                log(hid.getName().trim() + " was successfully opened for input", MSG.ANOUNCE);
            } catch (ArrayIndexOutOfBoundsException ae) {
                throw new IllegalArgumentException("Invalid device id (" + deviceId + "). Use io.ProbeIO to get information about available inputs.");
            }
        } else throw new IllegalArgumentException("Can't open module while deviceid is not set.");
    }

    @Override
    public void stop() {
        super.stop();
        if (timer!=null)
            timer.cancel();
        timer = null;
    }

    public void componentEvent(Event event) {
        Component comp = event.getComponent();
        String compName = toId(comp.getName());
        boolean hasMatch = false;
        for (Object[] trigMsg : triggerMessage) {
            String id = (String)((Object[])trigMsg[0])[0];
            if (id.equals("apos")) id = "'";
            
            if (id.equals(compName) ||
                    (id.equals("anyanalog") && comp.isAnalog()) ||
                    (id.equals("anydigital") && !comp.isAnalog())) {
                try {
                    Wildcard matcher = (Wildcard)((Object[])trigMsg[0])[1];
                    if (matcher.matches((double)event.getValue(), this)) {
                        varValue.setValue(event.getValue(), true);
                        varAnalog.setValue(comp.isAnalog(), true);
                        varRelative.setValue(comp.isRelative(), true);
                        varId.setValue(comp.getName().toLowerCase(), true);
                        setValue.set(varValue, localVars, this);
                        setAnalog.set(varAnalog, localVars, this);
                        setRelative.set(varRelative, localVars, this);
                        setId.set(varId, localVars, this);
                        if (infoFlag) log("Received registered HID event: " + toId(comp.getName()) + "=" + varValue, MSG.INFO);
                        hasMatch = true;
                        this.sendToOutputs((Variable)trigMsg[1], -1);
                    }
                } catch (IllegalArgumentException ie) {
                    log(ie.getMessage(), MSG.WARNING);
                }
            }
        }

       if (infoFlag && !hasMatch) log("Received HID event: " + toId(comp.getName()) + "=" + event.getValue(), MSG.INFO);
    }

    public static String[] getHIDIds() {
        Controller[] controllers = ControllerEnvironment.getDefaultEnvironment().getControllers();
        String[] ret = new String[controllers.length];
        HashMap<Type,Integer> count = new HashMap<Type,Integer>();
        count.put(Type.FINGERSTICK, 0);
        count.put(Type.GAMEPAD, 0);
        count.put(Type.HEADTRACKER, 0);
        count.put(Type.KEYBOARD, 0);
        count.put(Type.MOUSE, 0);
        count.put(Type.RUDDER, 0);
        count.put(Type.STICK, 0);
        count.put(Type.TRACKBALL, 0);
        count.put(Type.TRACKPAD, 0);
        count.put(Type.UNKNOWN, 0);
        count.put(Type.WHEEL, 0);

        for (int i=0; i<ret.length; i++) {
            String type = controllers[i].getType().toString().toLowerCase();
            int c = count.get(controllers[i].getType());
            if (c>0) type += c;
            ret[i] = type;
            count.put(controllers[i].getType(), c+1);
        }
        return ret;
    }

    private int findIndexByName(String deviceId) {
        String[] ids = getHIDIds();
        for (int i=0; i<ids.length; i++)
            if (ids[i].equals(deviceId)) return i;
        throw new IllegalArgumentException("Unknown deviceId: '" + deviceId + "'");
    }


    private String toId(String name) {
        return name.toLowerCase();
    }

    @Override
    protected void msgReceived(Variable in, long timestamp, Channel channel) {
    }

	class HIDUpdater extends TimerTask {
		Controller controller;
		float[] componentsval;
		List<HIDListener> listeners = new ArrayList<HIDListener>();
                int timesrun = 0;

		public HIDUpdater(Controller controller) {
			this.controller = controller;
			componentsval = new float[controller.getComponents().length];
			for (int i=0; i<componentsval.length; i++)
				componentsval[i]=0;
		}

		public void addListener(HIDListener listener) {
			listeners.add(listener);
		}

		public void run() {
            controller.poll();
            for (int i=0; i<controller.getComponents().length; i++) {
                if (componentsval[i]!=controller.getComponents()[i].getPollData()) {
                    componentsval[i]=controller.getComponents()[i].getPollData();
                    Event event = new Event();
                    event.set(controller.getComponents()[i], controller.getComponents()[i].getPollData(), 0);
                    for (HIDListener listener : listeners) {
                        if (timesrun<10) return;
                        else listener.componentEvent(event);
                    }
                }
            }
            if (timesrun<10) timesrun++;
        }
    }
}
interface HIDListener {
	public void componentEvent(Event event);
}